/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.linker;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.ScriptReference;
import com.google.gwt.core.ext.linker.StylesheetReference;
import com.google.gwt.core.ext.linker.impl.ResourceInjectionUtil;
import com.google.gwt.core.linker.IFrameLinker;
import com.google.gwt.util.tools.Utility;

/**
 * Special linker, remplacing the {@link IFrameLinker} linker, in order for parts to be able to be loaded dynamically without replacing the whole page. The linker
 * replace the IFrameTemplate template by a similar but customized template (a modified version of IFrameTemplate).
 * <p>
 * The problem with the original template is that it use document.write to write the loaded script. At load time (when the html page is loaded), document.write
 * write at the end of the current page, but once the page is loaded, any call to document.write imply an implicit call to document.open, thus replacing the
 * WHOLE html page.
 * <p>
 * As GWT modules are normally loaded at page load time, this is not a problem for normal GWT modules, but when trying to load modules dynamically, that is
 * after page load time (as it is the case with parts), this behavior is not acceptable. So the standard loader template is replaced with a similar but
 * customized one, that appends the script using DOM methods instead of calling document.write.
 * <p>
 * GWT use document.write to guarantee a certain loading order when multiples modules and external scripts are loaded in the same page (but always at load time,
 * not dynamically). This use case (dynamically loading of modules after page load time) is specifically NOT supported by <a href="http://code.google.com/p/google-web-toolkit/issues/detail?id=902">GTW</a>
 * <p>
 * The use of OPLinker may have consequences on IE compatibility, but we didn't tested and we don't care as IE is not supported.
 *
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 23 October 2009
 */
public class OPLinker extends IFrameLinker {

    /**
     * Return the description for this linker. Cannot be null.
     * @see com.google.gwt.core.linker.IFrameLinker#getDescription()
     */
    @Override
    public final String getDescription() {
        return "OPLinker";
    }

    /**
     * Return the path to the js loader template. Cannot be null.
     * @see com.google.gwt.core.linker.IFrameLinker#getSelectionScriptTemplate(com.google.gwt.core.ext.TreeLogger, com.google.gwt.core.ext.LinkerContext)
     */
    @Override
    protected final String getSelectionScriptTemplate(final TreeLogger logger, final LinkerContext context) {
        return "fr/openparts/OpenParts/linker/OPLoaderTemplate.js";
    }

    /**
     * The problem now is about scripts declared directly in .gwt.xml files.
     * The code that inject those scripts is in private statics methods of {@link ResourceInjectionUtil}, and unfortunately, this code call the dreadful "document.write" instruction, that will reload the whole page
     * in our case.
     * This means that we need to override the whole ResourceInjectionUtil class, and the code that call this class, in order to be able to change the "document.write" code by our code.
     * This means unfortunately a big copy of GWT code, just to change the call to ResourceInjectionUtil.
     */

    /*
     * Copyright 2008 Google Inc.
     * 
     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of
     * the License at
     * 
     * http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
     * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
     * 
     * @see com.google.gwt.core.ext.linker.impl.SelectionScriptLinker#fillSelectionScriptTemplate(java.lang.StringBuffer, com.google.gwt.core.ext.TreeLogger,
     * com.google.gwt.core.ext.LinkerContext, com.google.gwt.core.ext.linker.ArtifactSet, com.google.gwt.core.ext.linker.CompilationResult)
     */
    @Override
    protected String fillSelectionScriptTemplate(StringBuffer selectionScript, TreeLogger logger, LinkerContext context, ArtifactSet artifacts,
            CompilationResult result) throws UnableToCompleteException {

        String computeScriptBase;
        String processMetas;
        try {
            computeScriptBase = Utility.getFileFromClassPath(COMPUTE_SCRIPT_BASE_JS);
            processMetas = Utility.getFileFromClassPath(PROCESS_METAS_JS);
        } catch (IOException e) {
            logger.log(TreeLogger.ERROR, "Unable to read selection script template", e);
            throw new UnableToCompleteException();
        }
        replaceAll(selectionScript, "__COMPUTE_SCRIPT_BASE__", computeScriptBase);
        replaceAll(selectionScript, "__PROCESS_METAS__", processMetas);

        selectionScript = injectResources(selectionScript, artifacts);
        permutationsUtil.addPermutationsJs(selectionScript, logger, context);

        replaceAll(selectionScript, "__MODULE_FUNC__", context.getModuleFunctionName());
        replaceAll(selectionScript, "__MODULE_NAME__", context.getModuleName());
        replaceAll(selectionScript, "__HOSTED_FILENAME__", getHostedFilename());

        return selectionScript.toString();
    }
    
    /*
     * Copyright 2008 Google Inc.
     * 
     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of
     * the License at
     * 
     * http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
     * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
     */
    private StringBuffer injectResources(StringBuffer selectionScript, ArtifactSet artifacts) {
        // Add external dependencies
        int startPos = selectionScript.indexOf("// __MODULE_STYLES_END__");
        if (startPos != -1) {
            for (StylesheetReference resource : artifacts.find(StylesheetReference.class)) {
                String text = generateStylesheetInjector(resource.getSrc());
                selectionScript.insert(startPos, text);
                startPos += text.length();
            }
        }

        startPos = selectionScript.indexOf("// __MODULE_SCRIPTS_END__");
        if (startPos != -1) {
            for (ScriptReference resource : artifacts.find(ScriptReference.class)) {
                String text = generateScriptInjector(resource.getSrc());
                selectionScript.insert(startPos, text);
                startPos += text.length();
            }
        }
        return selectionScript;
    }

    /*
     * Mainly copyright 2008 Google Inc.
     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of
     * the License at
     * 
     * http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
     * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
     * 
     * 
     * The part contained in the  "if (opIsLoadingAsComponent)" block code is done by Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
     */
    private String generateScriptInjector(String scriptUrl) {
        if (isRelativeURL(scriptUrl)) {
            return "  if (!__gwt_scriptsLoaded['" + scriptUrl + "']) {\n" + 
                        "    __gwt_scriptsLoaded['" + scriptUrl + "'] = true;\n" +
            		    "    if (opIsLoadingAsComponent) {\n" +
            		    "      var elem2 = document.createElement('script');\n" +
            		    "      elem2.setAttribute('type', 'text/javascript');\n" +
            		    "      elem2.setAttribute('language', 'javascript');\n" +
                        "      elem2.setAttribute('src', base+'" + scriptUrl + "');\n" +
                        "      $doc.getElementsByTagName('head')[0].appendChild(elem2);\n" +
                        "    } else {\n" + 
            		    "      document.write('<script language=\\\"javascript\\\" src=\\\"'+base+'" + scriptUrl + "\\\"></script>');\n" + 
            		    "    }\n" + 
            		    "  }\n";
        } else {
            return "  if (!__gwt_scriptsLoaded['" + scriptUrl + "']) {\n" + 
                        "    __gwt_scriptsLoaded['" + scriptUrl + "'] = true;\n" + 
                        "    if (opIsLoadingAsComponent) {\n" +
                        "      var elem2 = document.createElement('script');\n" +
                        "      elem2.setAttribute('type', 'text/javascript');\n" +
                        "      elem2.setAttribute('language', 'javascript');\n" +
                        "      elem2.setAttribute('src', '" + scriptUrl + "');\n" +
                        "      $doc.getElementsByTagName('head')[0].appendChild(elem2);\n" +
                        "    } else {\n" + 
                        "      document.write('<script language=\\\"javascript\\\" src=\\\"" + scriptUrl + "\\\"></script>');\n" + 
                        "    }\n" + 
                        "  }\n";
        }
    }
    
    /*
     * Copyright 2008 Google Inc.
     * 
     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of
     * the License at
     * 
     * http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
     * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
     */
    private String generateStylesheetInjector(String stylesheetUrl) {
        String hrefExpr = "'" + stylesheetUrl + "'";
        if (isRelativeURL(stylesheetUrl)) {
          hrefExpr = "base + " + hrefExpr;
        }

        return "if (!__gwt_stylesLoaded['" + stylesheetUrl + "']) {\n           "
            + "  var l = $doc.createElement('link');\n                          "
            + "  __gwt_stylesLoaded['" + stylesheetUrl + "'] = l;\n             "
            + "  l.setAttribute('rel', 'stylesheet');\n                         "
            + "  l.setAttribute('href', " + hrefExpr + ");\n                    "
            + "  $doc.getElementsByTagName('head')[0].appendChild(l);\n         "
            + "}\n";
      }
    
    /*
     * Copyright 2008 Google Inc.
     * 
     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of
     * the License at
     * 
     * http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
     * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
     */
    private boolean isRelativeURL(String src) {
        if (src.startsWith("/")) {
          return false;
        }

        try {
          new URL(src);
          return false;
        } catch (MalformedURLException e) {
          // Do nothing, since it was a speculative parse.
        }

        return true;
      }
    
}